/*This source code read data from pcie device and store it as a file.
 * create device_op and file_op two pthreads to process in parallel, in order to
 * advance the performace then can fit the data recorder system requirement.
 **/
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h> 
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <errno.h>
#define FULL 1
#define EMPTY 0
#define DMA_SIZE 4*1024*1024
#define BUFFER_SIZE 16*1024*1024

/*Global variables*/
int buf_status[4]; //indicate the buffer is FULL or EMPTY
char *buf[4];
uint64_t data_len;

typedef struct _res{
	uint64_t data_len;
	char** argv;
	FILE* fp;
    int dev_id; 
}res_type;

struct timeval start, end;
static pthread_mutex_t mutex;

int res_init(res_type*);
int res_release(res_type*);
static void* file_op(FILE *fp);
static void* device_op(int dev_id);

int main(int argc, char** argv)
{
	FILE* fp;
    int dev_id; 
    /*argc processing*/
    if (argc < 2){
        printf("Incorrect arguments\n");
        printf("\t%s <size in MB> <output file>\n", argv[0]);
        return 1;
    }
	
	res_type *res= (res_type *) malloc( sizeof(res_type));

	if(res == NULL){
		printf("res allocate failed!\n");
		return ENOMEM;
	}
	
	res->argv = argv;
	/*resource init*/
	res_init(res);
    fp = res->fp;
    dev_id = res->dev_id;
    data_len = res->data_len;

    /*device_op and file_op pthread create*/
	pthread_t file_op_tid, device_op_tid;
	int file_op_rc=0, device_op_rc=0;
	device_op_rc = pthread_create(&device_op_tid, NULL, (void *)device_op, &dev_id );

	if(device_op_rc != 0)
		printf("%s: %d\n",__func__, strerror(device_op_rc));

	file_op_rc = pthread_create(&file_op_tid, NULL, (void *)file_op, fp);

	if(file_op_rc != 0)
		printf("%s: %d\n",__func__, strerror(file_op_rc));


	gettimeofday(&start,NULL);
	/*wait for file_op thread buf_status*/
	pthread_join(file_op_tid, NULL);
	gettimeofday(&end,NULL);

    float timeuse=1000000*(end.tv_sec-start.tv_sec)+(end.tv_usec-start.tv_usec); /* in usec*/
    float bw = res->data_len/timeuse*1000000/1024.0/1024.0; 
    printf("%d MB data captured.\n",(res->data_len)>>20);
    printf("timeuse is %lf us!\n", timeuse);
    printf("******Performace is : %lf MBps******\n",bw);
	
	/*resource release*/
	res_release(res);

	return 0;	

}
/*initialize resource:
 * process args
 * open device
 * open file
 * 4 buffers allocate
 * initialize buf_status[4]
 * initialize mutex
 * */
int res_init(res_type* res){

    /*get data_len*/
    char *endptr = NULL;
    uint64_t size = strtol(res->argv[1], &endptr, 10);
	if (endptr == res->argv[1] || size < 1) {
		printf("Please input a valid integral number (base 10)\n");
		exit(0);
	}
	res->data_len = size*1024ll*1024ll;

	/*open file*/
    res->fp = fopen(res->argv[2],"wb");

	if (res->fp == NULL){
	    perror("Error_opening file");
		return 2;
    }

    /*memory allocate*/
	buf[0] = (char *)malloc(BUFFER_SIZE + DMA_SIZE);
	buf[1] = (char *)malloc(BUFFER_SIZE + DMA_SIZE);
	buf[2] = (char *)malloc(BUFFER_SIZE + DMA_SIZE);
	buf[3] = (char *)malloc(BUFFER_SIZE + DMA_SIZE);

	if ((buf[1] == NULL) || (buf[0] == NULL) || (buf[2] == NULL) || (buf[3] == NULL)) {
		printf("Could not allocate buffer memory\n");
        return 3;
	}

    /*open pcie device*/
	res->dev_id = open("/dev/xbmd", O_RDWR);
   	if (res->dev_id <= 0) {
   		perror("Error opening xbmd device");
   		goto deverr;
   	}

    /*init mutex*/
    pthread_mutex_init(&mutex, NULL);

	/*init status*/
	pthread_mutex_lock(&mutex);
	buf_status[0] = EMPTY;
	buf_status[1] = EMPTY;
	buf_status[2] = EMPTY;
	buf_status[3] = EMPTY;
	pthread_mutex_unlock(&mutex);
	return 0;

deverr:
	free(buf[0]);
	free(buf[1]);
	free(buf[2]);
	free(buf[3]);
}

/*release the resource
 * */
int res_release(res_type* res){

	/*release memory*/
	free(buf[0]);
	free(buf[1]);
	free(buf[2]);
	free(buf[3]);

	/*close file and device*/
    fclose(res->fp);
	close(res->dev_id);

    /*destroy mutex*/
    pthread_mutex_destroy(&mutex);
	free(res);
	return 0;
}

/*store data from buffer to disk
 * */
static void* file_op(FILE *fp)
{
	long file_len = data_len;
	long offset = 0;
	int dma_session_size;
	int i = 0;
    
	while( file_len > 0 ){

		if(file_len >= BUFFER_SIZE)
			dma_session_size = BUFFER_SIZE;
		else
			dma_session_size =  file_len;

        /*check the buffer status*/
        /*FULL: write data to file
         *EMPTY:do nothing  
         * */
        pthread_mutex_lock(&mutex);
        if(buf_status[i & 0x3] == FULL)
        {
		    pthread_mutex_unlock(&mutex);
		    /*read data from pcie device and store as a file*/
		    fwrite(buf[i & 0x3], 1,dma_session_size,fp);
            
            /*set buffer status as EMPTY*/
            pthread_mutex_lock(&mutex);
            buf_status[i & 0x3] = EMPTY; 
		    pthread_mutex_unlock(&mutex);
            
            /*update data index*/
		    offset += dma_session_size;
		    file_len -= dma_session_size;
		    i++;
        }else
            /*do nothing*/
		    pthread_mutex_unlock(&mutex);
        
	}
	pthread_exit(0);	
}

/*read data from device
 * */
static void* device_op(int dev_id)
{

	uint64_t file_len = data_len;
	uint64_t offset=0;
	int dma_session_size;

	int i=0;
	while(file_len > 0 ){
		if(file_len >= BUFFER_SIZE)
			dma_session_size = BUFFER_SIZE;
		else
			dma_session_size = file_len;

        /*check the buffer status
         *EMPTY: read data from device
         *FULL: do nothing
         * */
        pthread_mutex_lock(&mutex);
        if(buf_status[i & 0x3] == EMPTY){
            pthread_mutex_unlock(&mutex);
            
            /*update the DRAM offset and read data*/
   			lseek(dev_id,offset, SEEK_SET);
    		read(dev_id, buf[i & 0x3], dma_session_size);

            /*update buffer status*/
            pthread_mutex_lock(&mutex);
            buf_status[i & 0x3]= FULL;
            pthread_mutex_unlock(&mutex);

            /*update data index*/
    	    offset += dma_session_size;
		    file_len -= dma_session_size;
		    i++; 
        }else
            /*do nothing*/
	        pthread_mutex_unlock(&mutex);
	}

	pthread_exit(0);
}
